/*!
 * SmartMenus jQuery Plugin - v0.9.7 - August 25, 2014
 * http://www.smartmenus.org/
 *
 * Copyright 2014 Vasil Dinkov, Vadikom Web Ltd.
 * http://vadikom.com
 *
 * Licensed MIT
 */

(function ($) {

    var menuTrees = [],
        IE = !!window.createPopup, // detect it for the iframe shim
        mouse = false, // optimize for touch by default - we will detect for mouse input
        mouseDetectionEnabled = false;

    // Handle detection for mouse input (i.e. desktop browsers, tablets with a mouse, etc.)
    function initMouseDetection(disable) {
        var eNS = '.smartmenus_mouse';
        if (!mouseDetectionEnabled && !disable) {
            // if we get two consecutive mousemoves within 2 pixels from each other and within 300ms, we assume a real mouse/cursor is present
            // in practice, this seems like impossible to trick unintentianally with a real mouse and a pretty safe detection on touch devices (even with older browsers that do not support touch events)
            var firstTime = true,
                lastMove = null;
            $(document).bind(getEventsNS([
                ['mousemove', function (e) {
                    var thisMove = {x: e.pageX, y: e.pageY, timeStamp: new Date().getTime()};
                    if (lastMove) {
                        var deltaX = Math.abs(lastMove.x - thisMove.x),
                            deltaY = Math.abs(lastMove.y - thisMove.y);
                        if ((deltaX > 0 || deltaY > 0) && deltaX <= 2 && deltaY <= 2 && thisMove.timeStamp - lastMove.timeStamp <= 300) {
                            mouse = true;
                            // if this is the first check after page load, check if we are not over some item by chance and call the mouseenter handler if yes
                            if (firstTime) {
                                var $a = $(e.target).closest('a');
                                if ($a.is('a')) {
                                    $.each(menuTrees, function () {
                                        if ($.contains(this.$root[0], $a[0])) {
                                            this.itemEnter({currentTarget: $a[0]});
                                            return false;
                                        }
                                    });
                                }
                                firstTime = false;
                            }
                        }
                    }
                    lastMove = thisMove;
                }],
                [touchEvents() ? 'touchstart' : 'pointerover pointermove pointerout MSPointerOver MSPointerMove MSPointerOut', function (e) {
                    if (isTouchEvent(e.originalEvent)) {
                        mouse = false;
                    }
                }]
            ], eNS));
            mouseDetectionEnabled = true;
        } else if (mouseDetectionEnabled && disable) {
            $(document).unbind(eNS);
            mouseDetectionEnabled = false;
        }
    }

    function isTouchEvent(e) {
        return !/^(4|mouse)$/.test(e.pointerType);
    }

    // we use this just to choose between toucn and pointer events when we need to, not for touch screen detection
    function touchEvents() {
        return 'ontouchstart' in window;
    }

    // returns a jQuery bind() ready object
    function getEventsNS(defArr, eNS) {
        if (!eNS) {
            eNS = '';
        }
        var obj = {};
        $.each(defArr, function (index, value) {
            obj[value[0].split(' ').join(eNS + ' ') + eNS] = value[1];
        });
        return obj;
    }

    $.SmartMenus = function (elm, options) {
        this.$root = $(elm);
        this.opts = options;
        this.rootId = ''; // internal
        this.$subArrow = null;
        this.subMenus = []; // all sub menus in the tree (UL elms) in no particular order (only real - e.g. UL's in mega sub menus won't be counted)
        this.activatedItems = []; // stores last activated A's for each level
        this.visibleSubMenus = []; // stores visible sub menus UL's
        this.showTimeout = 0;
        this.hideTimeout = 0;
        this.scrollTimeout = 0;
        this.clickActivated = false;
        this.zIndexInc = 0;
        this.$firstLink = null; // we'll use these for some tests
        this.$firstSub = null; // at runtime so we'll cache them
        this.disabled = false;
        this.$disableOverlay = null;
        this.isTouchScrolling = false;
        this.init();
    };

    $.extend($.SmartMenus, {
        hideAll: function () {
            $.each(menuTrees, function () {
                this.menuHideAll();
            });
        },
        destroy: function () {
            while (menuTrees.length) {
                menuTrees[0].destroy();
            }
            initMouseDetection(true);
        },
        prototype: {
            init: function (refresh) {
                var self = this;

                if (!refresh) {
                    menuTrees.push(this);

                    this.rootId = (new Date().getTime() + Math.random() + '').replace(/\D/g, '');

                    if (this.$root.hasClass('sm-rtl')) {
                        this.opts.rightToLeftSubMenus = true;
                    }

                    // init root (main menu)
                    var eNS = '.smartmenus';
                    this.$root
                        .data('smartmenus', this)
                        .attr('data-smartmenus-id', this.rootId)
                        .dataSM('level', 1)
                        .bind(getEventsNS([
                            ['mouseover focusin', $.proxy(this.rootOver, this)],
                            ['mouseout focusout', $.proxy(this.rootOut, this)]
                        ], eNS))
                        .delegate('a', getEventsNS([
                            ['mouseenter', $.proxy(this.itemEnter, this)],
                            ['mouseleave', $.proxy(this.itemLeave, this)],
                            ['mousedown', $.proxy(this.itemDown, this)],
                            ['focus', $.proxy(this.itemFocus, this)],
                            ['blur', $.proxy(this.itemBlur, this)],
                            ['click', $.proxy(this.itemClick, this)],
                            ['touchend', $.proxy(this.itemTouchEnd, this)]
                        ], eNS));

                    // hide menus on tap or click outside the root UL
                    eNS += this.rootId;
                    if (this.opts.hideOnClick) {
                        $(document).bind(getEventsNS([
                            ['touchstart', $.proxy(this.docTouchStart, this)],
                            ['touchmove', $.proxy(this.docTouchMove, this)],
                            ['touchend', $.proxy(this.docTouchEnd, this)],
                            // for Opera Mobile < 11.5, webOS browser, etc. we'll check click too
                            ['click', $.proxy(this.docClick, this)]
                        ], eNS));
                    }
                    // hide sub menus on resize
                    $(window).bind(getEventsNS([['resize orientationchange', $.proxy(this.winResize, this)]], eNS));

                    if (this.opts.subIndicators) {
                        this.$subArrow = $('<span/>').addClass('sub-arrow');
                        if (this.opts.subIndicatorsText) {
                            this.$subArrow.html(this.opts.subIndicatorsText);
                        }
                    }

                    // make sure mouse detection is enabled
                    initMouseDetection();
                }

                // init sub menus
                this.$firstSub = this.$root.find('ul').each(function () {
                    self.menuInit($(this));
                }).eq(0);

                this.$firstLink = this.$root.find('a').eq(0);

                // find current item
                if (this.opts.markCurrentItem) {
                    var reDefaultDoc = /(index|default)\.[^#\?\/]*/i,
                        reHash = /#.*/,
                        locHref = window.location.href.replace(reDefaultDoc, ''),
                        locHrefNoHash = locHref.replace(reHash, '');
                    this.$root.find('a').each(function () {
                        var href = this.href.replace(reDefaultDoc, ''),
                            $this = $(this);
                        if (href == locHref || href == locHrefNoHash) {
                            $this.addClass('current');
                            if (self.opts.markCurrentTree) {
                                $this.parent().parentsUntil('[data-smartmenus-id]', 'li').children('a').addClass('current');
                            }
                        }
                    });
                }
            },
            destroy: function () {
                this.menuHideAll();
                var eNS = '.smartmenus';
                this.$root
                    .removeData('smartmenus')
                    .removeAttr('data-smartmenus-id')
                    .removeDataSM('level')
                    .unbind(eNS)
                    .undelegate(eNS);
                eNS += this.rootId;
                $(document).unbind(eNS);
                $(window).unbind(eNS);
                if (this.opts.subIndicators) {
                    this.$subArrow = null;
                }
                var self = this;
                $.each(this.subMenus, function () {
                    if (this.hasClass('mega-menu')) {
                        this.find('ul').removeDataSM('in-mega');
                    }
                    if (this.dataSM('shown-before')) {
                        if (self.opts.subMenusMinWidth || self.opts.subMenusMaxWidth) {
                            this.css({width: '', minWidth: '', maxWidth: ''}).removeClass('sm-nowrap');
                        }
                        if (this.dataSM('scroll-arrows')) {
                            this.dataSM('scroll-arrows').remove();
                        }
                        this.css({zIndex: '', top: '', left: '', marginLeft: '', marginTop: '', display: ''});
                    }
                    if (self.opts.subIndicators) {
                        this.dataSM('parent-a').removeClass('has-submenu').children('span.sub-arrow').remove();
                    }
                    this.removeDataSM('shown-before')
                        .removeDataSM('ie-shim')
                        .removeDataSM('scroll-arrows')
                        .removeDataSM('parent-a')
                        .removeDataSM('level')
                        .removeDataSM('beforefirstshowfired')
                        .parent().removeDataSM('sub');
                });
                if (this.opts.markCurrentItem) {
                    this.$root.find('a.current').removeClass('current');
                }
                this.$root = null;
                this.$firstLink = null;
                this.$firstSub = null;
                if (this.$disableOverlay) {
                    this.$disableOverlay.remove();
                    this.$disableOverlay = null;
                }
                menuTrees.splice($.inArray(this, menuTrees), 1);
            },
            disable: function (noOverlay) {
                if (!this.disabled) {
                    this.menuHideAll();
                    // display overlay over the menu to prevent interaction
                    if (!noOverlay && !this.opts.isPopup && this.$root.is(':visible')) {
                        var pos = this.$root.offset();
                        this.$disableOverlay = $('<div class="sm-jquery-disable-overlay"/>').css({
                            position: 'absolute',
                            top: pos.top,
                            left: pos.left,
                            width: this.$root.outerWidth(),
                            height: this.$root.outerHeight(),
                            zIndex: this.getStartZIndex(true),
                            opacity: 0
                        }).appendTo(document.body);
                    }
                    this.disabled = true;
                }
            },
            docClick: function (e) {
                if (this.isTouchScrolling) {
                    this.isTouchScrolling = false;
                    return;
                }
                // hide on any click outside the menu or on a menu link
                if (this.visibleSubMenus.length && !$.contains(this.$root[0], e.target) || $(e.target).is('a')) {
                    this.menuHideAll();
                }
            },
            docTouchEnd: function (e) {
                if (!this.lastTouch) {
                    return;
                }
                if (this.visibleSubMenus.length && (this.lastTouch.x2 === undefined || this.lastTouch.x1 == this.lastTouch.x2) && (this.lastTouch.y2 === undefined || this.lastTouch.y1 == this.lastTouch.y2) && (!this.lastTouch.target || !$.contains(this.$root[0], this.lastTouch.target))) {
                    if (this.hideTimeout) {
                        clearTimeout(this.hideTimeout);
                        this.hideTimeout = 0;
                    }
                    // hide with a delay to prevent triggering accidental unwanted click on some page element
                    var self = this;
                    this.hideTimeout = setTimeout(function () {
                        self.menuHideAll();
                    }, 350);
                }
                this.lastTouch = null;
            },
            docTouchMove: function (e) {
                if (!this.lastTouch) {
                    return;
                }
                var touchPoint = e.originalEvent.touches[0];
                this.lastTouch.x2 = touchPoint.pageX;
                this.lastTouch.y2 = touchPoint.pageY;
            },
            docTouchStart: function (e) {
                var touchPoint = e.originalEvent.touches[0];
                this.lastTouch = {x1: touchPoint.pageX, y1: touchPoint.pageY, target: touchPoint.target};
            },
            enable: function () {
                if (this.disabled) {
                    if (this.$disableOverlay) {
                        this.$disableOverlay.remove();
                        this.$disableOverlay = null;
                    }
                    this.disabled = false;
                }
            },
            getClosestMenu: function (elm) {
                var $closestMenu = $(elm).closest('ul');
                while ($closestMenu.dataSM('in-mega')) {
                    $closestMenu = $closestMenu.parent().closest('ul');
                }
                return $closestMenu[0] || null;
            },
            getHeight: function ($elm) {
                return this.getOffset($elm, true);
            },
            // returns precise width/height float values
            getOffset: function ($elm, height) {
                var old;
                if ($elm.css('display') == 'none') {
                    old = {position: $elm[0].style.position, visibility: $elm[0].style.visibility};
                    $elm.css({position: 'absolute', visibility: 'hidden'}).show();
                }
                var box = $elm[0].getBoundingClientRect && $elm[0].getBoundingClientRect(),
                    val = box && (height ? box.height || box.bottom - box.top : box.width || box.right - box.left);
                if (!val && val !== 0) {
                    val = height ? $elm[0].offsetHeight : $elm[0].offsetWidth;
                }
                if (old) {
                    $elm.hide().css(old);
                }
                return val;
            },
            getStartZIndex: function (root) {
                var zIndex = parseInt(this[root ? '$root' : '$firstSub'].css('z-index'));
                if (!root && isNaN(zIndex)) {
                    zIndex = parseInt(this.$root.css('z-index'));
                }
                return !isNaN(zIndex) ? zIndex : 1;
            },
            getTouchPoint: function (e) {
                return e.touches && e.touches[0] || e.changedTouches && e.changedTouches[0] || e;
            },
            getViewport: function (height) {
                var name = height ? 'Height' : 'Width',
                    val = document.documentElement['client' + name],
                    val2 = window['inner' + name];
                if (val2) {
                    val = Math.min(val, val2);
                }
                return val;
            },
            getViewportHeight: function () {
                return this.getViewport(true);
            },
            getViewportWidth: function () {
                return this.getViewport();
            },
            getWidth: function ($elm) {
                return this.getOffset($elm);
            },
            handleEvents: function () {
                return !this.disabled && this.isCSSOn();
            },
            handleItemEvents: function ($a) {
                return this.handleEvents() && !this.isLinkInMegaMenu($a);
            },
            isCollapsible: function () {
                return this.$firstSub.css('position') == 'static';
            },
            isCSSOn: function () {
                return this.$firstLink.css('display') == 'block';
            },
            isFixed: function () {
                var isFixed = this.$root.css('position') == 'fixed';
                if (!isFixed) {
                    this.$root.parentsUntil('body').each(function () {
                        if ($(this).css('position') == 'fixed') {
                            isFixed = true;
                            return false;
                        }
                    });
                }
                return isFixed;
            },
            isLinkInMegaMenu: function ($a) {
                return !$a.parent().parent().dataSM('level');
            },
            isTouchMode: function () {
                return !mouse || this.isCollapsible();
            },
            itemActivate: function ($a) {
                var $li = $a.parent(),
                    $ul = $li.parent(),
                    level = $ul.dataSM('level');
                // if for some reason the parent item is not activated (e.g. this is an API call to activate the item), activate all parent items first
                if (level > 1 && (!this.activatedItems[level - 2] || this.activatedItems[level - 2][0] != $ul.dataSM('parent-a')[0])) {
                    var self = this;
                    $($ul.parentsUntil('[data-smartmenus-id]', 'ul').get().reverse()).add($ul).each(function () {
                        self.itemActivate($(this).dataSM('parent-a'));
                    });
                }
                // hide any visible deeper level sub menus
                if (this.visibleSubMenus.length > level) {
                    this.menuHideSubMenus(!this.activatedItems[level - 1] || this.activatedItems[level - 1][0] != $a[0] ? level - 1 : level);
                }
                // save new active item and sub menu for this level
                this.activatedItems[level - 1] = $a;
                this.visibleSubMenus[level - 1] = $ul;
                if (this.$root.triggerHandler('activate.smapi', $a[0]) === false) {
                    return;
                }
                // show the sub menu if this item has one
                var $sub = $li.dataSM('sub');
                if ($sub && (this.isTouchMode() || (!this.opts.showOnClick || this.clickActivated))) {
                    this.menuShow($sub);
                }
            },
            itemBlur: function (e) {
                var $a = $(e.currentTarget);
                if (!this.handleItemEvents($a)) {
                    return;
                }
                this.$root.triggerHandler('blur.smapi', $a[0]);
            },
            itemClick: function (e) {
                if (this.isTouchScrolling) {
                    this.isTouchScrolling = false;
                    e.stopPropagation();
                    return false;
                }
                var $a = $(e.currentTarget);
                if (!this.handleItemEvents($a)) {
                    return;
                }
                $a.removeDataSM('mousedown');
                if (this.$root.triggerHandler('click.smapi', $a[0]) === false) {
                    return false;
                }
                var $sub = $a.parent().dataSM('sub');
                if (this.isTouchMode()) {
                    // undo fix: prevent the address bar on iPhone from sliding down when expanding a sub menu
                    if ($a.dataSM('href')) {
                        $a.attr('href', $a.dataSM('href')).removeDataSM('href');
                    }
                    // if the sub is not visible
                    if ($sub && (!$sub.dataSM('shown-before') || !$sub.is(':visible'))) {
                        // try to activate the item and show the sub
                        this.itemActivate($a);
                        // if "itemActivate" showed the sub, prevent the click so that the link is not loaded
                        // if it couldn't show it, then the sub menus are disabled with an !important declaration (e.g. via mobile styles) so let the link get loaded
                        if ($sub.is(':visible')) {
                            return false;
                        }
                    }
                } else if (this.opts.showOnClick && $a.parent().parent().dataSM('level') == 1 && $sub) {
                    this.clickActivated = true;
                    this.menuShow($sub);
                    return false;
                }
                if ($a.hasClass('disabled')) {
                    return false;
                }
                if (this.$root.triggerHandler('select.smapi', $a[0]) === false) {
                    return false;
                }
            },
            itemDown: function (e) {
                var $a = $(e.currentTarget);
                if (!this.handleItemEvents($a)) {
                    return;
                }
                $a.dataSM('mousedown', true);
            },
            itemEnter: function (e) {
                var $a = $(e.currentTarget);
                if (!this.handleItemEvents($a)) {
                    return;
                }
                if (!this.isTouchMode()) {
                    if (this.showTimeout) {
                        clearTimeout(this.showTimeout);
                        this.showTimeout = 0;
                    }
                    var self = this;
                    this.showTimeout = setTimeout(function () {
                        self.itemActivate($a);
                    }, this.opts.showOnClick && $a.parent().parent().dataSM('level') == 1 ? 1 : this.opts.showTimeout);
                }
                this.$root.triggerHandler('mouseenter.smapi', $a[0]);
            },
            itemFocus: function (e) {
                var $a = $(e.currentTarget);
                if (!this.handleItemEvents($a)) {
                    return;
                }
                // fix (the mousedown check): in some browsers a tap/click produces consecutive focus + click events so we don't need to activate the item on focus
                if ((!this.isTouchMode() || !$a.dataSM('mousedown')) && (!this.activatedItems.length || this.activatedItems[this.activatedItems.length - 1][0] != $a[0])) {
                    this.itemActivate($a);
                }
                this.$root.triggerHandler('focus.smapi', $a[0]);
            },
            itemLeave: function (e) {
                var $a = $(e.currentTarget);
                if (!this.handleItemEvents($a)) {
                    return;
                }
                if (!this.isTouchMode()) {
                    if ($a[0].blur) {
                        $a[0].blur();
                    }
                    if (this.showTimeout) {
                        clearTimeout(this.showTimeout);
                        this.showTimeout = 0;
                    }
                }
                $a.removeDataSM('mousedown');
                this.$root.triggerHandler('mouseleave.smapi', $a[0]);
            },
            itemTouchEnd: function (e) {
                var $a = $(e.currentTarget);
                if (!this.handleItemEvents($a)) {
                    return;
                }
                // prevent the address bar on iPhone from sliding down when expanding a sub menu
                var $sub = $a.parent().dataSM('sub');
                if ($a.attr('href').charAt(0) !== '#' && $sub && (!$sub.dataSM('shown-before') || !$sub.is(':visible'))) {
                    $a.dataSM('href', $a.attr('href'));
                    $a.attr('href', '#');
                }
            },
            menuFixLayout: function ($ul) {
                // fixes a menu that is being shown for the first time
                if (!$ul.dataSM('shown-before')) {
                    $ul.hide().dataSM('shown-before', true);
                }
            },
            menuHide: function ($sub) {
                if (this.$root.triggerHandler('beforehide.smapi', $sub[0]) === false) {
                    return;
                }
                $sub.stop(true, true);
                if ($sub.is(':visible')) {
                    var complete = function () {
                        // unset z-index
                        $sub.css('z-index', '');
                    };
                    // if sub is collapsible (mobile view)
                    if (this.isCollapsible()) {
                        if (this.opts.collapsibleHideFunction) {
                            this.opts.collapsibleHideFunction.call(this, $sub, complete);
                        } else {
                            $sub.hide(this.opts.collapsibleHideDuration, complete);
                        }
                    } else {
                        if (this.opts.hideFunction) {
                            this.opts.hideFunction.call(this, $sub, complete);
                        } else {
                            $sub.hide(this.opts.hideDuration, complete);
                        }
                    }
                    // remove IE iframe shim
                    if ($sub.dataSM('ie-shim')) {
                        $sub.dataSM('ie-shim').remove();
                    }
                    // deactivate scrolling if it is activated for this sub
                    if ($sub.dataSM('scroll')) {
                        this.menuScrollStop($sub);
                        $sub.css({'touch-action': '', '-ms-touch-action': ''})
                            .unbind('.smartmenus_scroll').removeDataSM('scroll').dataSM('scroll-arrows').hide();
                    }
                    // unhighlight parent item
                    $sub.dataSM('parent-a').removeClass('highlighted');
                    var level = $sub.dataSM('level');
                    this.activatedItems.splice(level - 1, 1);
                    this.visibleSubMenus.splice(level - 1, 1);
                    this.$root.triggerHandler('hide.smapi', $sub[0]);
                }
            },
            menuHideAll: function () {
                if (this.showTimeout) {
                    clearTimeout(this.showTimeout);
                    this.showTimeout = 0;
                }
                // hide all subs
                this.menuHideSubMenus();
                // hide root if it's popup
                if (this.opts.isPopup) {
                    this.$root.stop(true, true);
                    if (this.$root.is(':visible')) {
                        if (this.opts.hideFunction) {
                            this.opts.hideFunction.call(this, this.$root);
                        } else {
                            this.$root.hide(this.opts.hideDuration);
                        }
                        // remove IE iframe shim
                        if (this.$root.dataSM('ie-shim')) {
                            this.$root.dataSM('ie-shim').remove();
                        }
                    }
                }
                this.activatedItems = [];
                this.visibleSubMenus = [];
                this.clickActivated = false;
                // reset z-index increment
                this.zIndexInc = 0;
            },
            menuHideSubMenus: function (level) {
                if (!level)
                    level = 0;
                for (var i = this.visibleSubMenus.length - 1; i > level; i--) {
                    this.menuHide(this.visibleSubMenus[i]);
                }
            },
            menuIframeShim: function ($ul) {
                // create iframe shim for the menu
                if (IE && this.opts.overlapControlsInIE && !$ul.dataSM('ie-shim')) {
                    $ul.dataSM('ie-shim', $('<iframe/>').attr({src: 'javascript:0', tabindex: -9})
                            .css({position: 'absolute', top: 'auto', left: '0', opacity: 0, border: '0'})
                    );
                }
            },
            menuInit: function ($ul) {
                if (!$ul.dataSM('in-mega')) {
                    this.subMenus.push($ul);
                    // mark UL's in mega drop downs (if any) so we can neglect them
                    if ($ul.hasClass('mega-menu')) {
                        $ul.find('ul').dataSM('in-mega', true);
                    }
                    // get level (much faster than, for example, using parentsUntil)
                    var level = 2,
                        par = $ul[0];
                    while ((par = par.parentNode.parentNode) != this.$root[0]) {
                        level++;
                    }
                    // cache stuff
                    $ul.dataSM('parent-a', $ul.prevAll('a').eq(-1))
                        .dataSM('level', level)
                        .parent().dataSM('sub', $ul);
                    // add sub indicator to parent item
                    if (this.opts.subIndicators) {
                        $ul.dataSM('parent-a').addClass('has-submenu')[this.opts.subIndicatorsPos](this.$subArrow.clone());
                    }
                }
            },
            menuPosition: function ($sub) {
                var $a = $sub.dataSM('parent-a'),
                    $ul = $sub.parent().parent(),
                    level = $sub.dataSM('level'),
                    subW = this.getWidth($sub),
                    subH = this.getHeight($sub),
                    itemOffset = $a.offset(),
                    itemX = itemOffset.left,
                    itemY = itemOffset.top,
                    itemW = this.getWidth($a),
                    itemH = this.getHeight($a),
                    $win = $(window),
                    winX = $win.scrollLeft(),
                    winY = $win.scrollTop(),
                    winW = this.getViewportWidth(),
                    winH = this.getViewportHeight(),
                    horizontalParent = $ul.hasClass('sm') && !$ul.hasClass('sm-vertical'),
                    subOffsetX = level == 2 ? this.opts.mainMenuSubOffsetX : this.opts.subMenusSubOffsetX,
                    subOffsetY = level == 2 ? this.opts.mainMenuSubOffsetY : this.opts.subMenusSubOffsetY,
                    x, y;
                if (horizontalParent) {
                    x = this.opts.rightToLeftSubMenus ? itemW - subW - subOffsetX : subOffsetX;
                    y = this.opts.bottomToTopSubMenus ? -subH - subOffsetY : itemH + subOffsetY;
                } else {
                    x = this.opts.rightToLeftSubMenus ? subOffsetX - subW : itemW - subOffsetX;
                    y = this.opts.bottomToTopSubMenus ? itemH - subOffsetY - subH : subOffsetY;
                }
                if (this.opts.keepInViewport && !this.isCollapsible()) {
                    var absX = itemX + x,
                        absY = itemY + y;
                    if (this.opts.rightToLeftSubMenus && absX < winX) {
                        x = horizontalParent ? winX - absX + x : itemW - subOffsetX;
                    } else if (!this.opts.rightToLeftSubMenus && absX + subW > winX + winW) {
                        x = horizontalParent ? winX + winW - subW - absX + x : subOffsetX - subW;
                    }
                    if (!horizontalParent) {
                        if (subH < winH && absY + subH > winY + winH) {
                            y += winY + winH - subH - absY;
                        } else if (subH >= winH || absY < winY) {
                            y += winY - absY;
                        }
                    }
                    // do we need scrolling?
                    // 0.49 used for better precision when dealing with float values
                    if (horizontalParent && (absY + subH > winY + winH + 0.49 || absY < winY) || !horizontalParent && subH > winH + 0.49) {
                        var self = this;
                        if (!$sub.dataSM('scroll-arrows')) {
                            $sub.dataSM('scroll-arrows', $([$('<span class="scroll-up"><span class="scroll-up-arrow"></span></span>')[0], $('<span class="scroll-down"><span class="scroll-down-arrow"></span></span>')[0]])
                                    .bind({
                                        mouseenter: function () {
                                            $sub.dataSM('scroll').up = $(this).hasClass('scroll-up');
                                            self.menuScroll($sub);
                                        },
                                        mouseleave: function (e) {
                                            self.menuScrollStop($sub);
                                            self.menuScrollOut($sub, e);
                                        },
                                        'mousewheel DOMMouseScroll': function (e) {
                                            e.preventDefault();
                                        }
                                    })
                                    .insertAfter($sub)
                            );
                        }
                        // bind scroll events and save scroll data for this sub
                        var eNS = '.smartmenus_scroll';
                        $sub.dataSM('scroll', {
                            step: 1,
                            // cache stuff for faster recalcs later
                            itemH: itemH,
                            subH: subH,
                            arrowDownH: this.getHeight($sub.dataSM('scroll-arrows').eq(1))
                        })
                            .bind(getEventsNS([
                                ['mouseover', function (e) {
                                    self.menuScrollOver($sub, e);
                                }],
                                ['mouseout', function (e) {
                                    self.menuScrollOut($sub, e);
                                }],
                                ['mousewheel DOMMouseScroll', function (e) {
                                    self.menuScrollMousewheel($sub, e);
                                }]
                            ], eNS))
                            .dataSM('scroll-arrows').css({
                                top: 'auto',
                                left: '0',
                                marginLeft: x + (parseInt($sub.css('border-left-width')) || 0),
                                width: subW - (parseInt($sub.css('border-left-width')) || 0) - (parseInt($sub.css('border-right-width')) || 0),
                                zIndex: $sub.css('z-index')
                            })
                            .eq(horizontalParent && this.opts.bottomToTopSubMenus ? 0 : 1).show();
                        // when a menu tree is fixed positioned we allow scrolling via touch too
                        // since there is no other way to access such long sub menus if no mouse is present
                        if (this.isFixed()) {
                            $sub.css({'touch-action': 'none', '-ms-touch-action': 'none'})
                                .bind(getEventsNS([
                                    [touchEvents() ? 'touchstart touchmove touchend' : 'pointerdown pointermove pointerup MSPointerDown MSPointerMove MSPointerUp', function (e) {
                                        self.menuScrollTouch($sub, e);
                                    }]
                                ], eNS));
                        }
                    }
                }
                $sub.css({top: 'auto', left: '0', marginLeft: x, marginTop: y - itemH});
                // IE iframe shim
                this.menuIframeShim($sub);
                if ($sub.dataSM('ie-shim')) {
                    $sub.dataSM('ie-shim').css({
                        zIndex: $sub.css('z-index'),
                        width: subW,
                        height: subH,
                        marginLeft: x,
                        marginTop: y - itemH
                    });
                }
            },
            menuScroll: function ($sub, once, step) {
                var data = $sub.dataSM('scroll'),
                    $arrows = $sub.dataSM('scroll-arrows'),
                    y = parseFloat($sub.css('margin-top')),
                    end = data.up ? data.upEnd : data.downEnd,
                    diff;
                if (!once && data.velocity) {
                    data.velocity *= 0.9;
                    diff = data.velocity;
                    if (diff < 0.5) {
                        this.menuScrollStop($sub);
                        return;
                    }
                } else {
                    diff = step || (once || !this.opts.scrollAccelerate ? this.opts.scrollStep : Math.floor(data.step));
                }
                // hide any visible deeper level sub menus
                var level = $sub.dataSM('level');
                if (this.visibleSubMenus.length > level) {
                    this.menuHideSubMenus(level - 1);
                }
                var newY = data.up && end <= y || !data.up && end >= y ? y : (Math.abs(end - y) > diff ? y + (data.up ? diff : -diff) : end);
                $sub.add($sub.dataSM('ie-shim')).css('margin-top', newY);
                // show opposite arrow if appropriate
                if (mouse && (data.up && newY > data.downEnd || !data.up && newY < data.upEnd)) {
                    $arrows.eq(data.up ? 1 : 0).show();
                }
                // if we've reached the end
                if (newY == end) {
                    if (mouse) {
                        $arrows.eq(data.up ? 0 : 1).hide();
                    }
                    this.menuScrollStop($sub);
                } else if (!once) {
                    if (this.opts.scrollAccelerate && data.step < this.opts.scrollStep) {
                        data.step += 0.5;
                    }
                    var self = this;
                    this.scrollTimeout = setTimeout(function () {
                        self.menuScroll($sub);
                    }, this.opts.scrollInterval);
                }
            },
            menuScrollMousewheel: function ($sub, e) {
                if (this.getClosestMenu(e.target) == $sub[0]) {
                    e = e.originalEvent;
                    var up = (e.wheelDelta || -e.detail) > 0;
                    if ($sub.dataSM('scroll-arrows').eq(up ? 0 : 1).is(':visible')) {
                        $sub.dataSM('scroll').up = up;
                        this.menuScroll($sub, true);
                    }
                }
                e.preventDefault();
            },
            menuScrollOut: function ($sub, e) {
                if (mouse) {
                    if (!/^scroll-(up|down)/.test((e.relatedTarget || '').className) && ($sub[0] != e.relatedTarget && !$.contains($sub[0], e.relatedTarget) || this.getClosestMenu(e.relatedTarget) != $sub[0])) {
                        $sub.dataSM('scroll-arrows').css('visibility', 'hidden');
                    }
                }
            },
            menuScrollOver: function ($sub, e) {
                if (mouse) {
                    if (!/^scroll-(up|down)/.test(e.target.className) && this.getClosestMenu(e.target) == $sub[0]) {
                        this.menuScrollRefreshData($sub);
                        var data = $sub.dataSM('scroll');
                        $sub.dataSM('scroll-arrows').eq(0).css('margin-top', data.upEnd).end()
                            .eq(1).css('margin-top', data.downEnd + data.subH - data.arrowDownH).end()
                            .css('visibility', 'visible');
                    }
                }
            },
            menuScrollRefreshData: function ($sub) {
                var data = $sub.dataSM('scroll'),
                    $win = $(window),
                    vportY = $win.scrollTop() - $sub.dataSM('parent-a').offset().top - data.itemH;
                $.extend(data, {
                    upEnd: vportY,
                    downEnd: vportY + this.getViewportHeight() - data.subH
                });
            },
            menuScrollStop: function ($sub) {
                if (this.scrollTimeout) {
                    clearTimeout(this.scrollTimeout);
                    this.scrollTimeout = 0;
                    $.extend($sub.dataSM('scroll'), {
                        step: 1,
                        velocity: 0
                    });
                    return true;
                }
            },
            menuScrollTouch: function ($sub, e) {
                e = e.originalEvent;
                if (isTouchEvent(e)) {
                    var touchPoint = this.getTouchPoint(e);
                    // neglect event if we touched a visible deeper level sub menu
                    if (this.getClosestMenu(touchPoint.target) == $sub[0]) {
                        var data = $sub.dataSM('scroll');
                        if (/(start|down)$/i.test(e.type)) {
                            if (this.menuScrollStop($sub)) {
                                // if we were scrolling, just stop and don't activate any link on the first touch
                                e.preventDefault();
                                this.isTouchScrolling = true;
                            } else {
                                this.isTouchScrolling = false;
                            }
                            // update scroll data since the user might have zoomed, etc.
                            this.menuScrollRefreshData($sub);
                            // extend it with the touch properties
                            $.extend(data, {
                                touchY: touchPoint.pageY,
                                touchTimestamp: e.timeStamp,
                                velocity: 0
                            });
                        } else if (/move$/i.test(e.type)) {
                            var prevY = data.touchY;
                            if (prevY !== undefined && prevY != touchPoint.pageY) {
                                this.isTouchScrolling = true;
                                $.extend(data, {
                                    up: prevY < touchPoint.pageY,
                                    touchY: touchPoint.pageY,
                                    touchTimestamp: e.timeStamp,
                                    velocity: data.velocity + Math.abs(touchPoint.pageY - prevY) * 0.5
                                });
                                this.menuScroll($sub, true, Math.abs(data.touchY - prevY));
                            }
                            e.preventDefault();
                        } else { // touchend/pointerup
                            if (data.touchY !== undefined) {
                                // check if we need to scroll
                                if (e.timeStamp - data.touchTimestamp < 120 && data.velocity > 0) {
                                    data.velocity *= 0.5;
                                    this.menuScrollStop($sub);
                                    this.menuScroll($sub);
                                    e.preventDefault();
                                }
                                delete data.touchY;
                            }
                        }
                    }
                }
            },
            menuShow: function ($sub) {
                if (!$sub.dataSM('beforefirstshowfired')) {
                    $sub.dataSM('beforefirstshowfired', true);
                    if (this.$root.triggerHandler('beforefirstshow.smapi', $sub[0]) === false) {
                        return;
                    }
                }
                if (this.$root.triggerHandler('beforeshow.smapi', $sub[0]) === false) {
                    return;
                }
                this.menuFixLayout($sub);
                $sub.stop(true, true);
                if (!$sub.is(':visible')) {
                    // set z-index
                    $sub.css('z-index', this.zIndexInc = (this.zIndexInc || this.getStartZIndex()) + 1);
                    // highlight parent item
                    if (this.opts.keepHighlighted || this.isCollapsible()) {
                        $sub.dataSM('parent-a').addClass('highlighted');
                    }
                    // min/max-width fix - no way to rely purely on CSS as all UL's are nested
                    if (this.opts.subMenusMinWidth || this.opts.subMenusMaxWidth) {
                        $sub.css({width: 'auto', minWidth: '', maxWidth: ''}).addClass('sm-nowrap');
                        if (this.opts.subMenusMinWidth) {
                            $sub.css('min-width', this.opts.subMenusMinWidth);
                        }
                        if (this.opts.subMenusMaxWidth) {
                            var noMaxWidth = this.getWidth($sub);
                            $sub.css('max-width', this.opts.subMenusMaxWidth);
                            if (noMaxWidth > this.getWidth($sub)) {
                                $sub.removeClass('sm-nowrap').css('width', this.opts.subMenusMaxWidth);
                            }
                        }
                    }
                    this.menuPosition($sub);
                    // insert IE iframe shim
                    if ($sub.dataSM('ie-shim')) {
                        $sub.dataSM('ie-shim').insertBefore($sub);
                    }
                    var complete = function () {
                        // fix: "overflow: hidden;" is not reset on animation complete in jQuery < 1.9.0 in Chrome when global "box-sizing: border-box;" is used
                        $sub.css('overflow', '');
                    };
                    // if sub is collapsible (mobile view)
                    if (this.isCollapsible()) {
                        if (this.opts.collapsibleShowFunction) {
                            this.opts.collapsibleShowFunction.call(this, $sub, complete);
                        } else {
                            $sub.show(this.opts.collapsibleShowDuration, complete);
                        }
                    } else {
                        if (this.opts.showFunction) {
                            this.opts.showFunction.call(this, $sub, complete);
                        } else {
                            $sub.show(this.opts.showDuration, complete);
                        }
                    }
                    // save new sub menu for this level
                    this.visibleSubMenus[$sub.dataSM('level') - 1] = $sub;
                    this.$root.triggerHandler('show.smapi', $sub[0]);
                }
            },
            popupHide: function (noHideTimeout) {
                if (this.hideTimeout) {
                    clearTimeout(this.hideTimeout);
                    this.hideTimeout = 0;
                }
                var self = this;
                this.hideTimeout = setTimeout(function () {
                    self.menuHideAll();
                }, noHideTimeout ? 1 : this.opts.hideTimeout);
            },
            popupShow: function (left, top) {
                if (!this.opts.isPopup) {
                    alert('SmartMenus jQuery Error:\n\nIf you want to show this menu via the "popupShow" method, set the isPopup:true option.');
                    return;
                }
                if (this.hideTimeout) {
                    clearTimeout(this.hideTimeout);
                    this.hideTimeout = 0;
                }
                this.menuFixLayout(this.$root);
                this.$root.stop(true, true);
                if (!this.$root.is(':visible')) {
                    this.$root.css({left: left, top: top});
                    // IE iframe shim
                    this.menuIframeShim(this.$root);
                    if (this.$root.dataSM('ie-shim')) {
                        this.$root.dataSM('ie-shim').css({
                            zIndex: this.$root.css('z-index'),
                            width: this.getWidth(this.$root),
                            height: this.getHeight(this.$root),
                            left: left,
                            top: top
                        }).insertBefore(this.$root);
                    }
                    // show menu
                    var self = this,
                        complete = function () {
                            self.$root.css('overflow', '');
                        };
                    if (this.opts.showFunction) {
                        this.opts.showFunction.call(this, this.$root, complete);
                    } else {
                        this.$root.show(this.opts.showDuration, complete);
                    }
                    this.visibleSubMenus[0] = this.$root;
                }
            },
            refresh: function () {
                this.menuHideAll();
                this.$root.find('ul').each(function () {
                    var $this = $(this);
                    if ($this.dataSM('scroll-arrows')) {
                        $this.dataSM('scroll-arrows').remove();
                    }
                })
                    .removeDataSM('in-mega')
                    .removeDataSM('shown-before')
                    .removeDataSM('ie-shim')
                    .removeDataSM('scroll-arrows')
                    .removeDataSM('parent-a')
                    .removeDataSM('level')
                    .removeDataSM('beforefirstshowfired');
                this.$root.find('a.has-submenu').removeClass('has-submenu')
                    .parent().removeDataSM('sub');
                if (this.opts.subIndicators) {
                    this.$root.find('span.sub-arrow').remove();
                }
                if (this.opts.markCurrentItem) {
                    this.$root.find('a.current').removeClass('current');
                }
                this.subMenus = [];
                this.init(true);
            },
            rootOut: function (e) {
                if (!this.handleEvents() || this.isTouchMode() || e.target == this.$root[0]) {
                    return;
                }
                if (this.hideTimeout) {
                    clearTimeout(this.hideTimeout);
                    this.hideTimeout = 0;
                }
                if (!this.opts.showOnClick || !this.opts.hideOnClick) {
                    var self = this;
                    this.hideTimeout = setTimeout(function () {
                        self.menuHideAll();
                    }, this.opts.hideTimeout);
                }
            },
            rootOver: function (e) {
                if (!this.handleEvents() || this.isTouchMode() || e.target == this.$root[0]) {
                    return;
                }
                if (this.hideTimeout) {
                    clearTimeout(this.hideTimeout);
                    this.hideTimeout = 0;
                }
            },
            winResize: function (e) {
                if (!this.handleEvents()) {
                    // we still need to resize the disable overlay if it's visible
                    if (this.$disableOverlay) {
                        var pos = this.$root.offset();
                        this.$disableOverlay.css({
                            top: pos.top,
                            left: pos.left,
                            width: this.$root.outerWidth(),
                            height: this.$root.outerHeight()
                        });
                    }
                    return;
                }
                // hide sub menus on resize - on mobile do it only on orientation change
                if (!this.isCollapsible() && (!('onorientationchange' in window) || e.type == 'orientationchange')) {
                    if (this.activatedItems.length) {
                        this.activatedItems[this.activatedItems.length - 1][0].blur();
                    }
                    this.menuHideAll();
                }
            }
        }
    });

    $.fn.dataSM = function (key, val) {
        if (val) {
            return this.data(key + '_smartmenus', val);
        }
        return this.data(key + '_smartmenus');
    }

    $.fn.removeDataSM = function (key) {
        return this.removeData(key + '_smartmenus');
    }

    $.fn.smartmenus = function (options) {
        if (typeof options == 'string') {
            var args = arguments,
                method = options;
            Array.prototype.shift.call(args);
            return this.each(function () {
                var smartmenus = $(this).data('smartmenus');
                if (smartmenus && smartmenus[method]) {
                    smartmenus[method].apply(smartmenus, args);
                }
            });
        }
        var opts = $.extend({}, $.fn.smartmenus.defaults, options);
        return this.each(function () {
            new $.SmartMenus(this, opts);
        });
    }

    // default settings
    $.fn.smartmenus.defaults = {
        isPopup: false,		// is this a popup menu (can be shown via the popupShow/popupHide methods) or a permanent menu bar
        mainMenuSubOffsetX: 0,		// pixels offset from default position
        mainMenuSubOffsetY: 0,		// pixels offset from default position
        subMenusSubOffsetX: 0,		// pixels offset from default position
        subMenusSubOffsetY: 0,		// pixels offset from default position
        subMenusMinWidth: '10em',		// min-width for the sub menus (any CSS unit) - if set, the fixed width set in CSS will be ignored
        subMenusMaxWidth: '20em',		// max-width for the sub menus (any CSS unit) - if set, the fixed width set in CSS will be ignored
        subIndicators: true,		// create sub menu indicators - creates a SPAN and inserts it in the A
        subIndicatorsPos: 'prepend',	// position of the SPAN relative to the menu item content ('prepend', 'append')
        subIndicatorsText: '+',		// [optionally] add text in the SPAN (e.g. '+') (you may want to check the CSS for the sub indicators too)
        scrollStep: 30,		// pixels step when scrolling long sub menus that do not fit in the viewport height
        scrollInterval: 30,		// interval between each scrolling step
        scrollAccelerate: true,		// accelerate scrolling or use a fixed step
        showTimeout: 250,		// timeout before showing the sub menus
        hideTimeout: 500,		// timeout before hiding the sub menus
        showDuration: 0,		// duration for show animation - set to 0 for no animation - matters only if showFunction:null
        showFunction: null,		// custom function to use when showing a sub menu (the default is the jQuery 'show')
        // don't forget to call complete() at the end of whatever you do
        // e.g.: function($ul, complete) { $ul.fadeIn(250, complete); }
        hideDuration: 0,		// duration for hide animation - set to 0 for no animation - matters only if hideFunction:null
        hideFunction: function ($ul, complete) {
            $ul.fadeOut(200, complete);
        },	// custom function to use when hiding a sub menu (the default is the jQuery 'hide')
        // don't forget to call complete() at the end of whatever you do
        // e.g.: function($ul, complete) { $ul.fadeOut(250, complete); }
        collapsibleShowDuration: 0,		// duration for show animation for collapsible sub menus - matters only if collapsibleShowFunction:null
        collapsibleShowFunction: function ($ul, complete) {
            $ul.slideDown(200, complete);
        },	// custom function to use when showing a collapsible sub menu
        // (i.e. when mobile styles are used to make the sub menus collapsible)
        collapsibleHideDuration: 0,		// duration for hide animation for collapsible sub menus - matters only if collapsibleHideFunction:null
        collapsibleHideFunction: function ($ul, complete) {
            $ul.slideUp(200, complete);
        },	// custom function to use when hiding a collapsible sub menu
        // (i.e. when mobile styles are used to make the sub menus collapsible)
        showOnClick: false,		// show the first-level sub menus onclick instead of onmouseover (matters only for mouse input)
        hideOnClick: true,		// hide the sub menus on click/tap anywhere on the page
        keepInViewport: true,		// reposition the sub menus if needed to make sure they always appear inside the viewport
        keepHighlighted: true,		// keep all ancestor items of the current sub menu highlighted (adds the 'highlighted' class to the A's)
        markCurrentItem: false,		// automatically add the 'current' class to the A element of the item linking to the current URL
        markCurrentTree: true,		// add the 'current' class also to the A elements of all ancestor items of the current item
        rightToLeftSubMenus: false,		// right to left display of the sub menus (check the CSS for the sub indicators' position)
        bottomToTopSubMenus: false,		// bottom to top display of the sub menus
        overlapControlsInIE: true		// make sure sub menus appear on top of special OS controls in IE (i.e. SELECT, OBJECT, EMBED, etc.)
    };

})(jQuery);